/* 
The NIST RCS (Real-time Control Systems) 
 library is public domain software, however it is preferred
 that the following disclaimers be attached.

Software Copywrite/Warranty Disclaimer

   This software was developed at the National Institute of Standards and
Technology by employees of the Federal Government in the course of their
official duties. Pursuant to title 17 Section 105 of the United States
Code this software is not subject to copyright protection and is in the
public domain. NIST Real-Time Control System software is an experimental
system. NIST assumes no responsibility whatsoever for its use by other
parties, and makes no guarantees, expressed or implied, about its
quality, reliability, or any other characteristic. We would appreciate
acknowledgement if the software is used. This software can be
redistributed and/or modified freely provided that any derivative works
bear some notice that they are derived from it, and any modified
versions bear some notice that they have been modified.



*/ 

/**********************************************************************
* File: memsem.cc
* Purpose: Uses a block of memory to implement a mutual exclusion semaphore.
* With LynxOs and SunOs using semop is very inefficient if the semaphore will
* ussually be available. Other platforms may give you no semaphore operations.
*
* Example: You are communicating with another process via shared memory. You
* need a mutual exclusion semaphore because you don't want the reader to
* occasionally read the buffer while the writer has written only part of the
* message, but most of the time when the writer won't be using buffer and
* the reader should get access immediately. semop will take 100 to 150
* microseconds to return access, while mem_get_access should take less than
* a microsecond with less than 10 total_connections.
*
* On 1/23/95 Will Shackleford began additions to allow for the buffer to
* be split. Splitting the buffer between doubles the ammount of memory
* required, but means that readers will never have to wait for a semaphore
* and decreases the percentage of time a writer will have to wait.
*
* This code will be obsolete when all operating systems provide efficient
* semaphore functions and all microprocessors have effective test and
* set instructions.
*************************************************************************/

#include <stddef.h>		/* NULL */

#include "timer.hh"		/* etime(), esleep() */
#include "memsem.hh"		/* struct mem_access_object */
#include "rcs_prnt.hh"		// rcs_print_error()

#define TIMEOUT_MIN ((double) 1.0E-6)

/******************************************************************
* Take the mutual exclusion semaphore.
*
* Parameters:
* data should point to an area of memory accesable to all processes that
*   is at least "total_connections" bytes long,
* connection_number is a unique identifier for this process it should
*  be between 0 and ("total_connections"-1)
* total_connections is the maximum number of processes that can use this
*  block of memory as a semaphore.
* timeout - is the time allowed for getting the semaphore.
*    (If timeout is negative then wait forever.)
* semdelay - is the time to wait between tries for the semaphore.
* read_only - Will the access to the buffer only to read the memory.
* split_buffer - The buffer will be split so that one area may be read
* while the other area is written to.
* toggle_bit - If the buffer is split the toggle bit determines which area
* will be read from and which may be written to. (O.K. its really a byte but
* think of it as 1 bit since it should only be 0 or 1)
*
* Returns: 0 for success: -1 for invalid parameters: or -2 if it timed out.
***********************************************************************/
#if 0
int
mem_get_access (void *data, long connection_number,
		long total_connections, double timeout, double semdelay,
		int read_only, int split_buffer, char &toggle_bit)
#endif
     int mem_get_access (struct mem_access_object *mo)
{
  register char *mylock;
  register char current_lock;
  register char *plock;
  char *lastlock;
  int semaphores_clear;
  double start_time, time;
  bool split_buffer = false;
  int read_only;
  int total_connections;
  int connection_number;
  double timeout;
#ifdef DEBUG
  rcs_print ("mem_get_access: - Time = %lf\n", etime ());
#endif
#ifdef DEBUG
  static int count = 0;
  count++;
#endif

  /* Check Parameters. */
  if ((total_connections = mo->total_connections) <=
      (connection_number = mo->connection_number) || connection_number < 0)
    {
      return -1;
    }
  if(total_connections <= 0)
    {
      return -1;
    }
  if (NULL == mo->data)
    {
      return -1;
    }

  /* Check for a request for me to sleep */
  int wait_requested = 1;
  lastlock = ((char *) mo->data) + total_connections;
  mylock = ((char *) mo->data) + connection_number;
  time = start_time = etime ();	/* get start time  */
  while (wait_requested
	 && (time - start_time < mo->timeout / 2 || mo->timeout < 0))
    {
      wait_requested = 0;
      for (plock = (char *) mo->data; plock < lastlock; plock++)
	{
	  if (5 == (current_lock = *plock) && plock != mylock)
	    {
	      wait_requested = 1;
	    }
	}
      if (wait_requested)
	{
	  esleep (mo->sem_delay);
	}
    }

  /* Try the locks one time before checking time because checking the
     locks generally takes much less time than checking the time. */
  *mylock = 4;
  mo->toggle_bit = ((char *) mo->data)[total_connections];
  read_only = mo->read_only;
#ifdef DEBUG
  if (read_only)
    {
      rcs_print ("added sleep: %d - Time = %lf\n", __LINE__, etime ());
      esleep (0.02);
    }
#endif
  if (read_only)
    {
      split_buffer = (bool) mo->split_buffer;
      if (split_buffer)
	{
	  *mylock = 2 + mo->toggle_bit;
	  return 0;
	}
      *mylock = 2;
    }
  else
    {
      *mylock = 1;
    }

#ifdef debug
  if (read_only)
    {
      rcs_print ("added sleep: %d - time = %lf\n", __line__, etime ());
      esleep (0.01);
    }
#endif
  semaphores_clear = 1;
  lastlock = ((char *) mo->data) + total_connections;
  mo->toggle_bit = ((char *) mo->data)[total_connections];
  for (plock = (char *) mo->data; plock < lastlock; plock++)
    {
      if (0 != (current_lock = *plock))
	{
	  if (plock != mylock)
	    {
	      if (!(read_only && current_lock > 1) &&
		  !(split_buffer && current_lock == 2 + mo->toggle_bit)
		  && (current_lock != 5))
		{
		  semaphores_clear = 0;
		}
	    }
	}
    }
#ifdef debug
  if (0)			//read_only && 0 == count % 2)
    {
      rcs_print ("added sleep: %d - time = %lf\n", __line__, etime ());
      esleep (0.01);
    }
#endif
  if (semaphores_clear)
    {
      return 0;
    }
  timeout = mo->timeout;
  if (timeout < TIMEOUT_MIN && timeout > 0)
    {
      *mylock = 0;
      return (-2);
    }
  /* release the lock before going to sleep. */
  *mylock = 5;

  if (NULL != mo->sem)
    {
      if (-1 == mo->sem->wait ())
	{
	  *mylock = 0;
	  return -1;
	}
    }
  else
    {
      esleep (mo->sem_delay);	/* sleep for 100 microseconds */
    }
  if (read_only)
    {
      *mylock = 2;
    }
  else
    {
      *mylock = 1;
    }

#ifdef debug
  if (0)			//read_only && 0 == count % 7)
    {
      rcs_print ("added sleep: %d - time = %lf\n", __line__, etime ());
      esleep (0.01);
    }
#endif
  while ((timeout >= 0) ? ((time - start_time) < timeout) : 1)
    {
      if (split_buffer)
	{
	  mo->toggle_bit = ((char *) mo->data)[total_connections];
	}
      semaphores_clear = 1;
      plock = (char *) mo->data;
      mo->toggle_bit = ((char *) mo->data)[total_connections];
      for (; plock < lastlock; plock++)
	{
	  current_lock = *plock;
	  if (0 != current_lock)
	    {
	      if (plock != mylock &&
		  !(read_only && current_lock > 1) &&
		  !(split_buffer && current_lock == 2 + mo->toggle_bit)
		  && (current_lock != 5))
		{
		  semaphores_clear = 0;
		}
	    }
	}
      if (semaphores_clear)
	{
	  return 0;
	}
      if (NULL != mo->sem)
	{
	  *mylock = 5;
	  mo->sem->wait ();
	}
      else
	{
	  *mylock = 5;
	  esleep (mo->sem_delay);	/* sleep for 100 microseconds */
	}
      if (read_only)
	{
	  *mylock = 2;
	}
      else
	{
	  *mylock = 1;
	}
#ifdef DEBUG
      if (0)			//read_only && 0 == count % 4)
	{
	  rcs_print ("added sleep: %d - Time = %lf\n", __LINE__, etime ());
	  esleep (0.01);
	}
#endif
      time = etime ();
    }
  *mylock = 0;
  return (-2);
}


/******************************************************************
* Give up the mutual exclusion semaphore.
*
* Parameters:
* data should point to an area of memory accesable to all processes that
*   is at least "total_connections" bytes long,
* connection_number is a unique identifier for this process it should
*  be between 0 and ("total_connections"-1)
*
* Returns: 0 for success: -1 for invalid parameters
***********************************************************************/
int
mem_release_access (struct mem_access_object *mo)
{
  int i;
  int process_waiting = 0;
#ifdef DEBUG
  rcs_print ("mem_release_access: - Time = %lf\n", etime ());
#endif
#ifdef DEBUG
  static int count = 0;
  count++;
#endif
  if (NULL == mo)
    {
      rcs_print_error ("mem_release_access: Invalid memory object.\n");
    }
  if (NULL == mo->data || mo->connection_number < 0)
    {
      rcs_print_error ("mem_release_access: Invalid memory object.\n");
      return -1;
    }
  if(mo->total_connections <= 0)
    {
      return -1;
    }

  if (mo->sem != NULL)
    {
      process_waiting = 0;
      for (i = 0; i < mo->total_connections; i++)
	{
	  if (((char *) mo->data)[i] == 5)
	    {
	      process_waiting = 1;
	      break;
	    }
	}
    }
#ifdef DEBUG
  if (0)			//(0 == count % 5)
    {
      rcs_print ("added sleep: %d - Time = %lf\n", __LINE__, etime ());
      esleep (0.01);
    }
#endif

  /* If were using a split buffer and this is the end of a write
     toggle the toggle bit. */
  if (mo->split_buffer && ((char *) mo->data)[mo->connection_number] == 1)
    {
      ((char *) mo->data)[mo->total_connections] = !(mo->toggle_bit);
    }
#ifdef DEBUG
  if (0)			// 0 == count % 7)
    {
      rcs_print ("added sleep: %d - Time = %lf\n", __LINE__, etime ());
      esleep (0.01);
    }
#endif

  ((char *) mo->data)[mo->connection_number] = 0;
#ifdef DEBUG
  if (0)			// 0 == count % 11)
    {
      rcs_print ("added sleep: %d - Time = %lf\n", __LINE__, etime ());
      esleep (0.01);
    }
#endif

  if (mo->sem != NULL)
    {
      if (process_waiting)
	{
	  mo->sem->post ();
	}
    }
  return (0);
}
